package com.zx.idc.backend.gui.common.base;

import com.google.common.collect.Lists;
import com.rxliuli.rxeasyexcel.EasyExcel;
import com.rxliuli.rxeasyexcel.ResponseHelper;
import com.rxliuli.rxeasyexcel.domain.ExcelImportError;
import com.rxliuli.rxeasyexcel.domain.ExcelReadContext;
import com.rxliuli.rxeasyexcel.domain.ExcelWriteContext;
import com.rxliuli.rxeasyexcel.domain.ImportDomain;
import com.rxliuli.rxeasyexcel.read.ExcelReader;
import com.zx.idc.backend.gui.common.util.HttpServletUtil;
import com.zx.idc.common.exception.ExcelImportErrorException;
import com.zx.idc.common.exception.GlobalException;
import com.zx.idc.common.lang.PathUtil;
import com.zx.idc.common.ref.ClassGenericUtil;
import com.zx.idc.common.spring.SpringConfigUtil;
import com.zx.idc.common.util.FileUtils;
import com.zx.idc.common.util.StreamUtils;
import com.zx.idc.ds.common.entity.BaseEntity;
import com.zx.idc.ds.common.service.BaseExcelService;
import com.zx.idc.ds.common.service.BaseService;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.UnexpectedRollbackException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;


/**
 * 基本的导入 API
 *
 * @author rxliuli
 */
public interface BaseExcelApi<Service extends BaseExcelService<? extends BaseService<? super Entity>, Entity>, Entity extends BaseEntity> {
    Logger _log = LoggerFactory.getLogger(BaseExcelApi.class);

    /**
     * 导出最大行数 65536
     */
    int EXCEL_ROWS_MAX_NUM = 65535;

    /**
     * 获取一个 Service，由子类实现
     * 实现这个方法可以更快的获取到 Service 以提高响应速度
     *
     * @return 服务类实现对象
     */
    default Optional<Service> getService() {
        return Optional.empty();
    }

    /**
     * 使用借贷模式使用服务类
     *
     * @param interfaceClazz API 接口的类
     * @param fn             自定义的方法
     * @param <T>            返回值类型
     * @return 自定义的方法的返回值
     * @throws GlobalException 如果找不到 Service 实现，则可能会抛出全局异常
     */
    default <T> T using2(Class<?> interfaceClazz, Function<Service, T> fn) {
        //noinspection unchecked
        final Service service = getService()
            .orElseGet(() -> (Service) BaseApiUtil.getGenericClassService(this.getClass(), interfaceClazz)
                .orElseThrow(() -> new GlobalException("继承 " + interfaceClazz.getName() + " 请务必配置泛型参数")));
        return fn.apply(service);
    }

    /**
     * 导出 Excel
     *
     * @param entity 导出的实体类
     */
    @GetMapping("/export-data")
    default void exportData(Entity entity) {
        final Class<Entity> clazz = (Class<Entity>) ClassGenericUtil.getInterfaceTypeParams(this.getClass()).get(BaseExcelApi.class).get(1);
        HttpServletResponse response = HttpServletUtil.getResponse();
        response.setContentType("application/octet-stream");
        response.setHeader("Content-Disposition", "attachment; filename=");
        OutputStream outputStream = null;
        try {
            outputStream = response.getOutputStream();
            List<Entity> entities = using2(BaseExcelApi.class, service -> service.exportData(entity));
            List<List<Entity>> partition = Lists.partition(entities, EXCEL_ROWS_MAX_NUM);

            String localSrc = SpringConfigUtil.get("spring.servlet.multipart.location");
            String tempDirName = "excel_" + System.nanoTime();
            String tempDirectoryPath = PathUtil.join(localSrc, tempDirName);
            FileUtils.createDirectory(tempDirectoryPath);
            int fileIndex = 1;
            for (List<Entity> entityList : partition) {
                String tmpFileName = String.format("part_%d.xlsx", fileIndex);
                String tempFilePath = PathUtil.join(tempDirectoryPath, tmpFileName);
                FileOutputStream fileOutputStream = null;
                FileUtils.createFile(tempFilePath);
                File file = new File(tempFilePath);
                try {
                    fileOutputStream = new FileOutputStream(file);
                    EasyExcel.export(fileOutputStream)
                        .export(ExcelWriteContext.builder()
                            .headers(clazz)
                            .datasource(entityList)
                            .build()
                        ).write();
                } finally {
                    StreamUtils.closeStream(fileOutputStream);
                }
                fileIndex++;
            }

            //生成临时压缩文件
            String tempZipFileName = tempDirName + ".zip";
            String tempZipFilePath = PathUtil.join(localSrc, tempZipFileName);
            FileUtils.zipFiles(tempDirectoryPath, "*", tempZipFilePath);
            FileUtils.deleteDirectory(new File(tempDirectoryPath));

            //将文件写入客户端
            File tempZipFile = new File(tempZipFilePath);
            try(InputStream inputStream = new FileInputStream(tempZipFile)){
                byte[] bytes = new byte[1024];
                int length = 0;
                while((length = inputStream.read(bytes)) != -1){
                    outputStream.write(bytes, 0, length);
                }
                outputStream.flush();
                response.flushBuffer();
            }catch (Exception e){
                _log.error("write zip file to client fail.", e);
            }finally{
                tempZipFile.delete();
            }

        } catch (Exception e) {
            _log.error("导出数据失败", e);
        } finally {
            try {
                if (outputStream != null) {
                    outputStream.flush();
                }
                StreamUtils.closeStream(outputStream);
            } catch (IOException e) {
                _log.error("close output stream fail", e);
            }
        }



    }

    /**
     * 导出 Excel 模板
     */
    @SuppressWarnings("unchecked")
    @GetMapping("/download-template")
    default void downloadTemplate() {
        final Class<Entity> clazz = (Class<Entity>) ClassGenericUtil.getInterfaceTypeParams(this.getClass()).get(BaseExcelApi.class).get(1);
        final OutputStream outputStream = ResponseHelper.wrapper(HttpServletUtil.getResponse(), "导出 Excel.xlsx");
        EasyExcel.export(outputStream)
            .export(ExcelWriteContext.builder(true)
                .headers(clazz)
                .datasource(Collections.emptyList())
                .build()
            )
            .write();
    }

    /**
     * 导入 Excel
     *
     * @param file 上传的 Excel 文件
     */
    @SuppressWarnings("unchecked")
    @PostMapping("/import-data")
    default void importData(@RequestParam("file") MultipartFile file) {
        try {
            byte[] byteArr = file.getBytes();
            try (
                final InputStream inputStream = new ByteArrayInputStream(byteArr);
                final ExcelReader excelReader = EasyExcel.read(inputStream)
            ) {
                final Class<Entity> clazz = (Class<Entity>) ClassGenericUtil.getInterfaceTypeParams(this.getClass()).get(BaseExcelApi.class).get(1);
                final ImportDomain<Entity> importDomain = excelReader.resolve(
                    ExcelReadContext.<Entity>builder()
                        .clazz(clazz)
                        .build()
                );
                if (CollectionUtils.isNotEmpty(importDomain.getErrors())) {
                    final OutputStream outputStream = ResponseHelper.wrapper(HttpServletUtil.getResponse(), "导出 Excel.xlsx");
                    EasyExcel.export(outputStream)
                        .export(ExcelWriteContext.builder(true)
                            .datasource(importDomain.getData())
                            .errors(importDomain.getErrors())
                            .build()
                        )
                        .write();
                    return;
                }
                using2(BaseExcelApi.class, service -> {
                    List<Entity> data = importDomain.getData();
                    if (data == null || data.isEmpty()) {
                        return null;
                    }
                    ImportDomain<Entity> domain;
                    try {
                        domain = service.insertImportData(data, importDomain.getColumnInfoMap());
                    } catch (ExcelImportErrorException e) {
                        domain = (ImportDomain) e.getData();
                    } catch (UnexpectedRollbackException e) {
                        _log.error("导入异常", e);
                        ExcelImportError excelImportError = new ExcelImportError(0, 0, "", null, e, "数据库错误，请稍后重试");
                        domain = importDomain;
                        if (domain.getErrors() == null) {
                            domain.setErrors(Lists.newArrayList(excelImportError));
                        } else {
                            domain.getErrors().add(excelImportError);
                        }
                    }
                    if (CollectionUtils.isNotEmpty(domain.getErrors())) {
                        final OutputStream outputStream = ResponseHelper.wrapper(HttpServletUtil.getResponse(), "导出 Excel.xlsx");
                        EasyExcel.export(outputStream)
                            .export(ExcelWriteContext.builder(true)
                                .datasource(domain.getData())
                                .errors(domain.getErrors())
                                .build()
                            )
                            .write();
                    }
                    return null;
                });
            }
        } catch (IOException e) {
            _log.error("导入异常", e);
        }
    }
}
